#include <errno.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sched.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <string.h>
#include <aura.h>
#include <auraplus.h>
#include <linknode.h>

#include "thread.h"
#include <pthread.h>

#undef NULL
#include "private.h"

extern pthread_mutex_t threadLock;

DLinkNode g_ThreadInfoList;
Aura_Handle s_ThreadLock;

typedef struct _ThreadArg {
    PAuraThreadMain m_pEntry;
    Aura_PVoid           m_pArgs;
    Aura_Bool         m_bIsSuspend;
    ThreadInfo*     m_pThreadInfo;
}ThreadArgument;

extern pthread_key_t s_key;

EXTERN ThreadInfo *GetCurrentThreadInfo()
{
    ThreadInfo *pThreadInfo = NULL;

    pThreadInfo = (ThreadInfo *)pthread_getspecific(s_key);
    if (NULL == pThreadInfo) {
        printf(" faield to getsepcific.");
        return NULL;
    }
    return pThreadInfo;
}

EXTERN void _AllThread_SetPriority(Aura_ThreadPriorityRank PriorityRank)
{
    ThreadInfo *pThreadInfo;
    START_LOCK(threadLock);
    ForEachDLinkNode(ThreadInfo *, pThreadInfo, &g_ThreadInfoList) {
        AuraThread_SetPriority((Aura_Handle)pThreadInfo, PriorityRank);
    }
    END_LOCK(threadLock);
}

EXTERN void _AllThread_GetPriority(Aura_ThreadPriorityRank *pPriorityRank)
{
    ThreadInfo *pThreadInfo;
    pThreadInfo = GetCurrentThreadInfo();
    assert(NULL != pThreadInfo);

    AuraThread_GetPriority((Aura_Handle)pThreadInfo, pPriorityRank);
}

// for "Thread"

int StartThread(Aura_PVoid pArg)
{
    ThreadArgument * pThreadArg = (ThreadArgument *)pArg;
    ThreadInfo *pThreadInfo;
    PAuraThreadMain pEntry;
    Aura_PVoid           pArgs;
    Aura_Bool         bIsSuspend;

    pEntry = pThreadArg->m_pEntry;
    pArgs = pThreadArg->m_pArgs;
    bIsSuspend = pThreadArg->m_bIsSuspend;
    pThreadInfo = pThreadArg->m_pThreadInfo;
    free(pThreadArg);

    pThreadInfo->m_iThreadId = (Aura_ThreadId)getpid();

    if(bIsSuspend){
        AuraThread_Suspend((Aura_Handle)pThreadInfo);
    }

    pEntry(pArgs);

    AuraThread_Quit(0);

    return 0;
}

#define STACK_SIZE 4*1024*1024

AURA_API AuraThread_Create(
        /* [in] */ PAuraThreadMain pEntry,
        /* [in] */ Aura_PVoid pArgument,
        /* [in] */ Aura_ThreadCreationFlags creationFlags,
        /* [out] */ Aura_Handle * phThread,
        /* [out] */ Aura_ThreadId * pThreadId)
{
    ThreadArgument * pArg;
    char *pStack;
    ThreadInfo *pThreadInfo;
    int tid;

    pArg = (ThreadArgument *)malloc (sizeof(ThreadArgument));
    if(NULL == pArg){
        return AURA_E_OUT_OF_MEMORY;
    }

    pStack = (char *)malloc(STACK_SIZE);
    if(NULL == pStack){
        free(pArg);
        return AURA_E_OUT_OF_MEMORY;
    }

    pThreadInfo = new ThreadInfo();
    if(NULL == pThreadInfo){
        free(pArg);
        free(pStack);
        return AURA_E_OUT_OF_MEMORY;
    }

    pThreadInfo->m_pStack = pStack;
    pArg->m_pEntry = pEntry;
    pArg->m_pArgs = pArgument;
    pArg->m_pThreadInfo = pThreadInfo;
    if (creationFlags & Aura_ThreadCreationFlag_Unstarted) {
        pArg->m_bIsSuspend = AURA_TRUE;
    }else {
        pArg->m_bIsSuspend = AURA_FALSE;
    }

    Lock(s_ThreadLock);
    g_ThreadInfoList.InsertNext(pThreadInfo);
    Unlock(s_ThreadLock);

    tid = clone(StartThread,
        pStack + STACK_SIZE - 1,
        CLONE_FS|CLONE_FILES|CLONE_VM|CLONE_SIGHAND,//|SIGCHLD,
        (Aura_PVoid)pArg);
    if(-1 == tid){
        free(pArg);
        free(pStack);
        Lock(s_ThreadLock);
        pThreadInfo->Detach();
        Unlock(s_ThreadLock);
        delete pThreadInfo;

        return AURA_E_INVALID_OPERATION;
    }

    pThreadInfo->m_iThreadId = (Aura_ThreadId)tid;

    if(NULL != phThread){
        *phThread = (Aura_Handle *)pThreadInfo;
    }
    if(NULL != pThreadId){
        *pThreadId = (Aura_ThreadId)tid ;
    }

    return AURA_NOERROR;
}

AURA_API AuraThread_Resume(
        /* [in] */ Aura_Handle hThread)
{
    ThreadInfo *pThreadInfo = (ThreadInfo *)hThread;

    if((NULL == pThreadInfo)
        || (!IsValidHandle(ThreadInfo, pThreadInfo))){
        return AURA_E_INVALID_ARGUMENT;
    }

    if(SUSPENDING & pThreadInfo->m_uState){
        pThreadInfo->m_uState &= ~SUSPENDING;
        kill(pThreadInfo->m_iThreadId, SIGCONT);
    }

    return AURA_NOERROR;
}

AURA_API AuraThread_Suspend(
        /* [in] */ Aura_Handle hThread)
{
    ThreadInfo *pThreadInfo = (ThreadInfo *)hThread;

    if((NULL == pThreadInfo)
        || (!IsValidHandle(ThreadInfo, pThreadInfo))){
        return AURA_E_INVALID_ARGUMENT;
    }

    pThreadInfo->m_uState |= SUSPENDING;

    kill(pThreadInfo->m_iThreadId, SIGSTOP);

    return AURA_NOERROR;
}

AURA_API AuraThread_Join(
        /* [in] */ Aura_Handle hThread,
        /* [in] */ Aura_Millisecond32 timeOut,
        /* [out] */ Aura_WaitResult * pResult)
{
    FixMe("Not implemented yet.");
    return AURA_E_NOT_IMPLEMENTED;
}

AURA_API AuraThread_Abort(
        /* [in] */ Aura_Handle hThread,
        /* [in] */ Aura_QuitCode quitCode)
{
    ThreadInfo *pThreadInfo = (ThreadInfo *)hThread;

    if((NULL == pThreadInfo)
        || (!IsValidHandle(ThreadInfo, pThreadInfo))){
        return AURA_E_INVALID_ARGUMENT;
    }

    pThreadInfo->m_uState |= QUIT;
    kill(pThreadInfo->m_iThreadId, SIGKILL);

    return AURA_NOERROR;
}

AURA_API AuraThread_Interrupt(
        /* [in] */ Aura_Handle hThread)
{
    //may be use the teb for ipc
    ThreadInfo *pThreadInfo = (ThreadInfo *)hThread;

    if((NULL == pThreadInfo)
        || (!IsValidHandle(ThreadInfo, pThreadInfo))){
        return AURA_E_INVALID_ARGUMENT;
    }

    pThreadInfo->m_uState |= INTERRUPT;
    kill(pThreadInfo->m_iThreadId, SIGINT);

    return AURA_NOERROR;
}

AURA_API AuraThread_Destroy(
        /* [in] */ Aura_Handle hThread)
{
    ThreadInfo *pThreadInfo = (ThreadInfo *)hThread;

    if((NULL == pThreadInfo)
        || (!IsValidHandle(ThreadInfo, pThreadInfo))){
        return AURA_E_INVALID_ARGUMENT;
    }

    if(NULL != pThreadInfo->m_pStack){
        free(pThreadInfo->m_pStack);
        pThreadInfo->m_pStack = NULL;
    }

    Lock(s_ThreadLock);
    pThreadInfo->Detach();
    Unlock(s_ThreadLock);

    delete pThreadInfo;

    return AURA_NOERROR;
}

AURA_API AuraThread_GetPriority(
        /* [in] */ Aura_Handle hThread,
        /* [out] */ Aura_ThreadPriority * pPriority)
{
    ThreadInfo *pThreadInfo = (ThreadInfo *)hThread;
    struct sched_param sSchedParam;


    if((NULL == pPriority)
        ||(NULL == pThreadInfo)
        || (!IsValidHandle(ThreadInfo, pThreadInfo))){
        return AURA_E_INVALID_ARGUMENT;
    }

    if(-1 == sched_getparam(pThreadInfo->m_iThreadId, &sSchedParam)){
        return AURA_E_INVALID_OPERATION;
    }

    *pPriority = sSchedParam.sched_priority;

    return AURA_NOERROR;
}

AURA_API AuraThread_SetPriority(
        /* [in] */ Aura_Handle hThread,
        /* [in] */ Aura_ThreadPriority priority)
{
    ThreadInfo *pThreadInfo = (ThreadInfo *)hThread;
    struct sched_param sSchedParam;

    if((NULL == pThreadInfo)
        || (!IsValidHandle(ThreadInfo, pThreadInfo))){
        return AURA_E_INVALID_ARGUMENT;
    }

    if(-1 == sched_getparam(pThreadInfo->m_iThreadId, &sSchedParam)){
        return AURA_E_INVALID_OPERATION;
    }

    sSchedParam.sched_priority = priority;

    sched_setparam(pThreadInfo->m_iThreadId, &sSchedParam);

    return AURA_NOERROR;

}

AURA_API AuraThread_GetProcessorTimes(
        /* [in] */ Aura_Handle hThread,
        /* [out] */  Aura_Millisecond32 * pUserTime,
        /* [out] */  Aura_Millisecond32 * pPrivilegedTime)
{
    struct rusage usage;
    int ret;

    if((NULL == pUserTime)
        || (NULL == pPrivilegedTime))
    {
        return AURA_E_INVALID_ARGUMENT;
    }

    ThreadInfo *pThreadInfo = (ThreadInfo *)hThread;

    if((NULL == pThreadInfo)
        || (!IsValidHandle(ThreadInfo, pThreadInfo))){
        return AURA_E_INVALID_ARGUMENT;
    }


    ret = getrusage(RUSAGE_SELF, &usage);
    if(-1 == ret){
        return AURA_E_INVALID_OPERATION;
    }

    *pUserTime = usage.ru_utime.tv_sec*1000 + usage.ru_utime.tv_usec/1000;
    *pPrivilegedTime = usage.ru_stime.tv_sec*1000 + usage.ru_stime.tv_usec/1000;

    return AURA_NOERROR;
}

AURA_API_(Aura_Void) AuraThread_Sleep(
        /* [in] */ Aura_Millisecond32 timeOut,
        /* [out] */ Aura_WaitResult * pResult)
{
    struct timespec tTimeReq, tTimeRem;
    ThreadInfo *pThreadInfo;
    int ret = 0;

    pThreadInfo = GetCurrentThreadInfo();
    assert(NULL != pThreadInfo);

    tTimeReq.tv_sec = timeOut / (1000);
    tTimeReq.tv_nsec = (timeOut % (1000)) * 1000 *1000;

    pThreadInfo->m_uState &= WAITING;

    do {
        ret = nanosleep(&tTimeReq, &tTimeRem);
        if(-1 == ret){
            if(EINTR == errno){
                if(pThreadInfo->m_uState & INTERRUPT){
                    pThreadInfo->m_uState &= ~INTERRUPT;
                    if(NULL != pResult){
                        *pResult = Aura_WaitResult_Interrupted;
                    }
                    return; // AURA_NOERROR;
                }else {
                    memcpy(&tTimeReq, &tTimeRem, sizeof(struct timespec));
                }
            } else {
                return; // AURA_E_INVALID_OPERATION;
            }
        }
    } while (-1 == ret);

    if(NULL != pResult){
        *pResult = Aura_WaitResult_TimedOut;
    }

}

AURA_API_(Aura_Void) AuraThread_Yield()
{
    sched_yield();
}

AURA_API_(Aura_Void) AuraThread_Quit(
        /* [in] */ Aura_QuitCode quitCode)
{
    FixMe("Not implemented yet.");
    //return AURA_E_NOT_IMPLEMENTED;
}

AURA_API_(Aura_Handle) AuraThread_GetCurrent()
{
    DLinkNode *pNext = NULL;
    DLinkNode *pHead = &g_ThreadInfoList;
    ThreadInfo *pThreadInfo = NULL;

    pThreadInfo = (ThreadInfo *)pthread_getspecific(s_key);
    if (NULL == pThreadInfo) {
        printf(" faield to getsepcific.");
        return NULL;
    }
    return (Aura_Handle)pThreadInfo;
}

AURA_API_(Aura_ThreadId) AuraThread_GetCurrentId()
{
    return (Aura_ThreadId)getpid();
}
